﻿using System;
using GeoAPI.Geometries;
using NetTopologySuite.Algorithm;
using NetTopologySuite.Geometries;
using NetTopologySuite.Operation.Distance;

namespace NetTopologySuite.Operation.Distance3D
{
	public class Distance3DOp
	{
		public static double Distance(IGeometry g0, IGeometry g1)
		{
			return new Distance3DOp(g0, g1).Distance();
		}

		public static bool IsWithinDistance(IGeometry g0, IGeometry g1, double distance)
		{
			return new Distance3DOp(g0, g1, distance).Distance() <= distance;
		}

		public static Coordinate[] NearestPoints(IGeometry g0, IGeometry g1)
		{
			return new Distance3DOp(g0, g1).NearestPoints();
		}

		public Distance3DOp(IGeometry g0, IGeometry g1) : this(g0, g1, 0.0)
		{
		}

		public Distance3DOp(IGeometry g0, IGeometry g1, double terminateDistance)
		{
			this._geom = new IGeometry[2];
			this._geom[0] = g0;
			this._geom[1] = g1;
			this._terminateDistance = terminateDistance;
		}

		public double Distance()
		{
			if (this._geom[0] == null || this._geom[1] == null)
			{
				throw new ArgumentException("null geometries are not supported");
			}
			if (this._geom[0].IsEmpty || this._geom[1].IsEmpty)
			{
				return 0.0;
			}
			this.ComputeMinDistance();
			return this._minDistance;
		}

		public Coordinate[] NearestPoints()
		{
			this.ComputeMinDistance();
			return new Coordinate[]
			{
				this._minDistanceLocation[0].Coordinate,
				this._minDistanceLocation[1].Coordinate
			};
		}

		public GeometryLocation[] NearestLocations()
		{
			this.ComputeMinDistance();
			return this._minDistanceLocation;
		}

		private void UpdateDistance(double dist, GeometryLocation loc0, GeometryLocation loc1, bool flip)
		{
			this._minDistance = dist;
			int num = flip ? 1 : 0;
			this._minDistanceLocation[num] = loc0;
			this._minDistanceLocation[1 - num] = loc1;
			if (this._minDistance < this._terminateDistance)
			{
				this._isDone = true;
			}
		}

		private void ComputeMinDistance()
		{
			if (this._minDistanceLocation != null)
			{
				return;
			}
			this._minDistanceLocation = new GeometryLocation[2];
			int num = this.MostPolygonalIndex();
			bool flip = num == 0;
			this.ComputeMinDistanceMultiMulti(this._geom[num], this._geom[1 - num], flip);
		}

		private int MostPolygonalIndex()
		{
			Dimension dimension = this._geom[0].Dimension;
			Dimension dimension2 = this._geom[1].Dimension;
			if (dimension >= Dimension.Surface && dimension2 >= Dimension.Surface)
			{
				if (this._geom[0].NumPoints > this._geom[1].NumPoints)
				{
					return 0;
				}
				return 1;
			}
			else
			{
				if (dimension >= Dimension.Surface)
				{
					return 0;
				}
				if (dimension2 >= Dimension.Surface)
				{
					return 1;
				}
				return 0;
			}
		}

		private void ComputeMinDistanceMultiMulti(IGeometry g0, IGeometry g1, bool flip)
		{
			if (g0 is IGeometryCollection)
			{
				int numGeometries = g0.NumGeometries;
				for (int i = 0; i < numGeometries; i++)
				{
					IGeometry geometryN = g0.GetGeometryN(i);
					this.ComputeMinDistanceMultiMulti(geometryN, g1, flip);
					if (this._isDone)
					{
						return;
					}
				}
				return;
			}
			if (g0.IsEmpty)
			{
				return;
			}
			if (g0 is IPolygon)
			{
				this.ComputeMinDistanceOneMulti(Distance3DOp.PolyPlane(g0), g1, flip);
				return;
			}
			this.ComputeMinDistanceOneMulti(g0, g1, flip);
		}

		private void ComputeMinDistanceOneMulti(IGeometry g0, IGeometry g1, bool flip)
		{
			if (g1 is IGeometryCollection)
			{
				int numGeometries = g1.NumGeometries;
				for (int i = 0; i < numGeometries; i++)
				{
					IGeometry geometryN = g1.GetGeometryN(i);
					this.ComputeMinDistanceOneMulti(g0, geometryN, flip);
					if (this._isDone)
					{
						return;
					}
				}
				return;
			}
			this.ComputeMinDistance(g0, g1, flip);
		}

		private void ComputeMinDistanceOneMulti(PlanarPolygon3D poly, IGeometry geom, bool flip)
		{
			if (geom is IGeometryCollection)
			{
				int numGeometries = geom.NumGeometries;
				for (int i = 0; i < numGeometries; i++)
				{
					IGeometry geometryN = geom.GetGeometryN(i);
					this.ComputeMinDistanceOneMulti(poly, geometryN, flip);
					if (this._isDone)
					{
						return;
					}
				}
				return;
			}
			if (geom is IPoint)
			{
				this.ComputeMinDistancePolygonPoint(poly, (IPoint)geom, flip);
				return;
			}
			if (geom is ILineString)
			{
				this.ComputeMinDistancePolygonLine(poly, (ILineString)geom, flip);
				return;
			}
			if (geom is IPolygon)
			{
				this.ComputeMinDistancePolygonPolygon(poly, (IPolygon)geom, flip);
			}
		}

		private static PlanarPolygon3D PolyPlane(IGeometry poly)
		{
			return new PlanarPolygon3D((IPolygon)poly);
		}

		private void ComputeMinDistance(IGeometry g0, IGeometry g1, bool flip)
		{
			if (g0 is IPoint)
			{
				if (g1 is IPoint)
				{
					this.ComputeMinDistancePointPoint((IPoint)g0, (IPoint)g1, flip);
					return;
				}
				if (g1 is ILineString)
				{
					this.ComputeMinDistanceLinePoint((ILineString)g1, (IPoint)g0, !flip);
					return;
				}
				if (g1 is IPolygon)
				{
					this.ComputeMinDistancePolygonPoint(Distance3DOp.PolyPlane(g1), (IPoint)g0, !flip);
					return;
				}
			}
			if (g0 is ILineString)
			{
				if (g1 is IPoint)
				{
					this.ComputeMinDistanceLinePoint((ILineString)g0, (IPoint)g1, flip);
					return;
				}
				if (g1 is ILineString)
				{
					this.ComputeMinDistanceLineLine((ILineString)g0, (ILineString)g1, flip);
					return;
				}
				if (g1 is IPolygon)
				{
					this.ComputeMinDistancePolygonLine(Distance3DOp.PolyPlane(g1), (ILineString)g0, !flip);
					return;
				}
			}
			if (g0 is IPolygon)
			{
				if (g1 is IPoint)
				{
					this.ComputeMinDistancePolygonPoint(Distance3DOp.PolyPlane(g0), (IPoint)g1, flip);
					return;
				}
				if (g1 is ILineString)
				{
					this.ComputeMinDistancePolygonLine(Distance3DOp.PolyPlane(g0), (ILineString)g1, flip);
					return;
				}
				if (g1 is IPolygon)
				{
					this.ComputeMinDistancePolygonPolygon(Distance3DOp.PolyPlane(g0), (IPolygon)g1, flip);
				}
			}
		}

		private void ComputeMinDistancePolygonPolygon(PlanarPolygon3D poly0, IPolygon poly1, bool flip)
		{
			this.ComputeMinDistancePolygonRings(poly0, poly1, flip);
			if (this._isDone)
			{
				return;
			}
			PlanarPolygon3D poly2 = new PlanarPolygon3D(poly1);
			this.ComputeMinDistancePolygonRings(poly2, poly0.Polygon, flip);
		}

		private void ComputeMinDistancePolygonRings(PlanarPolygon3D poly, IPolygon ringPoly, bool flip)
		{
			this.ComputeMinDistancePolygonLine(poly, ringPoly.ExteriorRing, flip);
			if (this._isDone)
			{
				return;
			}
			int numInteriorRings = ringPoly.NumInteriorRings;
			for (int i = 0; i < numInteriorRings; i++)
			{
				this.ComputeMinDistancePolygonLine(poly, ringPoly.GetInteriorRingN(i), flip);
				if (this._isDone)
				{
					return;
				}
			}
		}

		private void ComputeMinDistancePolygonLine(PlanarPolygon3D poly, ILineString line, bool flip)
		{
			Coordinate coordinate = Distance3DOp.Intersection(poly, line);
			if (coordinate != null)
			{
				this.UpdateDistance(0.0, new GeometryLocation(poly.Polygon, 0, coordinate), new GeometryLocation(line, 0, coordinate), flip);
				return;
			}
			this.ComputeMinDistanceLineLine(poly.Polygon.ExteriorRing, line, flip);
			if (this._isDone)
			{
				return;
			}
			int numInteriorRings = poly.Polygon.NumInteriorRings;
			for (int i = 0; i < numInteriorRings; i++)
			{
				this.ComputeMinDistanceLineLine(poly.Polygon.GetInteriorRingN(i), line, flip);
				if (this._isDone)
				{
					return;
				}
			}
		}

		private static Coordinate Intersection(PlanarPolygon3D poly, ILineString line)
		{
			ICoordinateSequence coordinateSequence = line.CoordinateSequence;
			if (coordinateSequence.Count == 0)
			{
				return null;
			}
			Coordinate coordinate = new Coordinate();
			coordinateSequence.GetCoordinate(0, coordinate);
			double num = poly.Plane.OrientedDistance(coordinate);
			Coordinate coordinate2 = new Coordinate();
			for (int i = 0; i < coordinateSequence.Count - 1; i++)
			{
				coordinateSequence.GetCoordinate(i, coordinate);
				coordinateSequence.GetCoordinate(i + 1, coordinate2);
				double num2 = poly.Plane.OrientedDistance(coordinate2);
				if (num * num2 <= 0.0)
				{
					Coordinate coordinate3 = Distance3DOp.SegmentPoint(coordinate, coordinate2, num, num2);
					if (poly.Intersects(coordinate3))
					{
						return coordinate3;
					}
					num = num2;
				}
			}
			return null;
		}

		private void ComputeMinDistancePolygonPoint(PlanarPolygon3D polyPlane, IPoint point, bool flip)
		{
			Coordinate coordinate = point.Coordinate;
			ILineString exteriorRing = polyPlane.Polygon.ExteriorRing;
			if (polyPlane.Intersects(coordinate, exteriorRing))
			{
				int numInteriorRings = polyPlane.Polygon.NumInteriorRings;
				for (int i = 0; i < numInteriorRings; i++)
				{
					ILineString interiorRingN = polyPlane.Polygon.GetInteriorRingN(i);
					if (polyPlane.Intersects(coordinate, interiorRingN))
					{
						this.ComputeMinDistanceLinePoint(interiorRingN, point, flip);
						return;
					}
				}
				double dist = System.Math.Abs(polyPlane.Plane.OrientedDistance(coordinate));
				this.UpdateDistance(dist, new GeometryLocation(polyPlane.Polygon, 0, coordinate), new GeometryLocation(point, 0, coordinate), flip);
			}
			this.ComputeMinDistanceLinePoint(exteriorRing, point, flip);
		}

		private void ComputeMinDistanceLineLine(ILineString line0, ILineString line1, bool flip)
		{
			Coordinate[] coordinates = line0.Coordinates;
			Coordinate[] coordinates2 = line1.Coordinates;
			for (int i = 0; i < coordinates.Length - 1; i++)
			{
				for (int j = 0; j < coordinates2.Length - 1; j++)
				{
					double num = CGAlgorithms3D.DistanceSegmentSegment(coordinates[i], coordinates[i + 1], coordinates2[j], coordinates2[j + 1]);
					if (num < this._minDistance)
					{
						this._minDistance = num;
						LineSegment lineSegment = new LineSegment(coordinates[i], coordinates[i + 1]);
						LineSegment line2 = new LineSegment(coordinates2[j], coordinates2[j + 1]);
						Coordinate[] array = lineSegment.ClosestPoints(line2);
						this.UpdateDistance(num, new GeometryLocation(line0, i, array[0]), new GeometryLocation(line1, j, array[1]), flip);
					}
					if (this._isDone)
					{
						return;
					}
				}
			}
		}

		private void ComputeMinDistanceLinePoint(ILineString line, IPoint point, bool flip)
		{
			Coordinate[] coordinates = line.Coordinates;
			Coordinate coordinate = point.Coordinate;
			for (int i = 0; i < coordinates.Length - 1; i++)
			{
				double num = CGAlgorithms3D.DistancePointSegment(coordinate, coordinates[i], coordinates[i + 1]);
				if (num < this._minDistance)
				{
					Coordinate pt = new LineSegment(coordinates[i], coordinates[i + 1]).ClosestPoint(coordinate);
					this.UpdateDistance(num, new GeometryLocation(line, i, pt), new GeometryLocation(point, 0, coordinate), flip);
				}
				if (this._isDone)
				{
					return;
				}
			}
		}

		private void ComputeMinDistancePointPoint(IPoint point0, IPoint point1, bool flip)
		{
			double num = CGAlgorithms3D.Distance(point0.Coordinate, point1.Coordinate);
			if (num < this._minDistance)
			{
				this.UpdateDistance(num, new GeometryLocation(point0, 0, point0.Coordinate), new GeometryLocation(point1, 0, point1.Coordinate), flip);
			}
		}

		private static Coordinate SegmentPoint(Coordinate p0, Coordinate p1, double d0, double d1)
		{
			if (d0 <= 0.0)
			{
				return new Coordinate(p0);
			}
			if (d1 <= 0.0)
			{
				return new Coordinate(p1);
			}
			double num = System.Math.Abs(d0) / (System.Math.Abs(d0) + System.Math.Abs(d1));
			double x = p0.X + num * (p1.X - p0.X);
			double y = p0.Y + num * (p1.Y - p0.Y);
			double z = p0.Z + num * (p1.Z - p0.Z);
			return new Coordinate(x, y, z);
		}

		private readonly IGeometry[] _geom;

		private readonly double _terminateDistance;

		private GeometryLocation[] _minDistanceLocation;

		private double _minDistance = double.MaxValue;

		private bool _isDone;
	}
}
